package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.feigns.AdminFeign;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vo.HotArticleVo;
import com.heima.model.common.constants.article.ArticleConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.exception.CustException;
import com.heima.model.mess.app.AggBehaviorDTO;
import com.heima.utils.common.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Version: V1.0
 */
@Slf4j
@Service
@Transactional
public class HotArticleServiceImpl implements HotArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private AdminFeign adminFeign;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 计算热文章
     */
    @Override
    public void computeHotArticle() {
        // 1 查询近五天的文章
        // 1.1 计算5天前的时间
        String before5Day = LocalDateTime.now().minusDays(5L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 1.2 调用mapper层方法查询所有文章数据
        List<ApArticle> apArticleList = apArticleMapper.selectArticleByDate(before5Day);
        if (CollectionUtils.isEmpty(apArticleList)) {
            log.error("当前5天内没有文章数据");
            return;
        }
        // 2 计算每一篇文章的热度得分
        List<HotArticleVo> articleVoList = getHotArticleVoList(apArticleList);
        // 3 按照频道 每个频道缓存  对应的热度最高的前30条文章
        cacheToRedisByTag(articleVoList);
        // TODO:4 推荐频道  缓存所有文章中热度最高的30篇文章

    }

    /**
     * 更新热点文章分值
     *
     * @param aggBehavior
     */
    @Override
    public void updateApArticle(AggBehaviorDTO aggBehavior) {
        // 1 根据id 查询文章数据
        ApArticle article = apArticleMapper.selectById(aggBehavior.getArticleId());
        if (article == null) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST, "对应的文章数据不存在");
        }
        // 2 将聚合行为文章数据  和 文章表中的统计数据  累加一起
        // 更新阅读数量
        Integer views = article.getViews() == null ? 0 : article.getViews(); // 获取文章本来的浏览数据
        article.setViews((int) (views + aggBehavior.getView()));
        // 更新点赞数量
        Integer likes = article.getLikes() == null ? 0 : article.getLikes();
        article.setLikes((int) (likes + aggBehavior.getLike()));
        // 更新评论数量
        Integer comment = article.getComment() == null ? 0 : article.getComment();
        article.setLikes((int) (comment + aggBehavior.getComment()));
        // 更新收藏数量
        Integer collect = article.getCollection() == null ? 0 : article.getCollection();
        article.setLikes((int) (collect + aggBehavior.getCollect()));
        apArticleMapper.updateById(article);
        // 3 重新计算文章热度得分
        Integer score = computeScore(article);
        // 4 如果文章是今天发布的  热度 * 3
        // 当前时间
        String nowDateStr = DateUtils.dateToString(new Date());
        // 文章的发布时间
        String publishDateStr = DateUtils.dateToString(article.getPublishTime());
        if (nowDateStr.equals(publishDateStr)) {
            score = score * 3;
        }
        // 5 更新当前文章所在频道的redis缓存
        updateApArticleCache(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());
        // 6 更新推荐频道的redis 缓存
        updateApArticleCache(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 更新当前文章所在频道的redis缓存 或者 更新推荐频道的redis 缓存
     *
     * @param article
     * @param score
     * @param cacheKey
     */
    private void updateApArticleCache(ApArticle article, Integer score, String cacheKey) {
        // 1 从redis中查询出 对应的热点文章列表
        String hotArticleJsonStr = redisTemplate.opsForValue().get(cacheKey);
        List<HotArticleVo> hotArticleVoList = JSON.parseArray(hotArticleJsonStr, HotArticleVo.class);
        // 2 判断当前文章是否 存在热点文章列表中
        boolean isHas = false;
        for (HotArticleVo hotArticleVo : hotArticleVoList) {
            // 3 如果存在  直接更新文章 score热度值
            if (hotArticleVo.getId().equals(article.getId())) {
                // 存在 直接更新热度得分
                hotArticleVo.setScore(score);
                isHas = true;
                break;
            }
        }
        if (!isHas) {
            // 4 如果不存在 直接将当前文章 加入到热点文章列表
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(article, hotArticleVo);
            hotArticleVo.setScore(score);
            hotArticleVoList.add(hotArticleVo);
        }
        // 5 重新将热点文章列表 按照热度降序排序   截取前30条文章
        hotArticleVoList = hotArticleVoList.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        // 6 将文章集合 重新存入到redis中
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleVoList));
    }

    /**
     * 按照频道 每个频道缓存  对应的热度最高的前30条文章
     *
     * @param articleVoList
     */
    private void cacheToRedisByTag(List<HotArticleVo> articleVoList) {
        // 1 远程查询频道列表
        ResponseResult<List<AdChannel>> result = adminFeign.selectChannels();
        if (!result.checkCode()) {
            CustException.cust(AppHttpCodeEnum.REMOTE_SERVER_ERROR, result.getErrorMessage());
        }
        List<AdChannel> channelList = result.getData();
        // 2 遍历频道列表  从文章列表中筛选每隔频道对应的文章  调用保存方法 sortAndCache
        for (AdChannel adChannel : channelList) {
            // 将参数articleVoList中的文章按照频道id 全部缓存到redis中
            List<HotArticleVo> hotArticlesByTag = articleVoList.stream()
                    .filter(articleVo -> articleVo.getChannelId().equals(adChannel.getId()))
                    .collect(Collectors.toList());
            sortAndCache(hotArticlesByTag, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + adChannel.getId());
        }
        // 3 缓存推荐频道的文章 调用保存方法  sortAndCache
        sortAndCache(articleVoList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 缓存文章
     *
     * @param hotArticleList
     * @param cacheKey
     */
    private void sortAndCache(List<HotArticleVo> hotArticleList, String cacheKey) {
        // 1 按照热度降序排序  截取前30条文章
        //  排序默认升序  reversed 取反 则为降序
        hotArticleList = hotArticleList.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        // 2 缓存到redis中 字符串形式
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(hotArticleList));
    }

    /**
     * 计算每一篇文章的热度得分
     *
     * @param articleList
     * @return
     */
    private List<HotArticleVo> getHotArticleVoList(List<ApArticle> articleList) {
        // 遍历文章
        List<HotArticleVo> result = articleList.stream().map(
                apArticle -> {
                    HotArticleVo hotArticleVo = new HotArticleVo();
                    // 拷贝 apArticle 属性到HotArticleVo 中
                    BeanUtils.copyProperties(apArticle, hotArticleVo);
                    // 计算每篇文章的热度得分
                    Integer score = computeScore(apArticle);
                    hotArticleVo.setScore(score);
                    return hotArticleVo;
                }
        ).collect(Collectors.toList());
        return result;
    }

    /**
     * 计算每天文章的热度得分
     *
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = null;
        // 阅读 权重: 1
        Integer views = apArticle.getViews();
        if (views != null) {
            score += views * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        // 点赞 权重: 3
        Integer likes = apArticle.getLikes();
        if (likes != null) {
            score += likes * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        // 评论 权重: 5
        Integer comment = apArticle.getComment();
        if (comment != null) {
            score += comment * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        // 收藏 权重: 8
        Integer collection = apArticle.getCollection();
        if (collection != null) {
            score += collection * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }
}